package com.xuecheng.gateway.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * @version 1.0
 * @description 网关认证过虑器
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {


    //白名单  这里相当于OJ服务的对内部调用的权限配置
    private static List<String> whitelist = null;

    static {
        //加载白名单 网关过滤器 在配置类里面首先定义了一个过滤器去判断请求的地址是否在白名单里面 如果在则进行放行 如果不是则开始校验token
        try (
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist= new ArrayList<>(strings);

        } catch (Exception e) {
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }


    }
    //这个就是在认证服务的下发的令牌的存储方式 后面改成的jwt
    //这里使用了依赖注入，将一个 TokenStore 的实例注入到这个类中。TokenStore 通常用于存储和管理令牌（token）。
    @Autowired
    private TokenStore tokenStore;

    //ServerWebExchange 获取当前请求的路径 也可以获取到token
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        //创建一个路径匹配器，用于后面的路径匹配操作 在OJ判题里面也是用到了
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
            if (pathMatcher.match(url, requestUrl)) {
                return chain.filter(exchange);
            }
        }

        //检查token是否存在  getToken是在下面定义的方法 获取请求路径中的token
        String token = getToken(exchange);
        if (StringUtils.isBlank(token)) {
            return buildReturnMono("没有认证",exchange);
        }
        //判断是否是有效的token
        OAuth2AccessToken oAuth2AccessToken;
        try {
            //使用 TokenStore 中的 readAccessToken 方法尝试读取指定的令牌 因为在security中把令牌放到了tokenStore中，所以需要对token在tokenStore中进行查找
            //根据提供的令牌字符串（token），在 TokenStore 中查找相应的 OAuth2AccessToken 对象。这涉及到从存储介质（如内存、数据库或缓存）中检索令牌数据。
            //如果找到对应的令牌，readAccessToken 方法会返回一个 OAuth2AccessToken 实例，这个实例包含有关令牌的信息，例如：令牌的有效期（是否过期）
            oAuth2AccessToken = tokenStore.readAccessToken(token);
            //判断令牌是否过期，如果过期则返回错误信息 "认证令牌已过期"。
            boolean expired = oAuth2AccessToken.isExpired();
            if (expired) {
                return buildReturnMono("认证令牌已过期",exchange);
            }
            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效",exchange);
        }

    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        //"Authorization"是Spring Security默认用于JWT令牌的标头名称
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) {
            return null;
        }
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return token;
    }



    //在 Web 开发中，当需要返回未经授权的错误信息时，可以使用这个方法来构建响应体，并将其包装在 Mono 对象中，以便进行响应处理。
    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        String jsonString = JSON.toJSONString(new RestErrorResponse(error));
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }


    @Override
    //把这个过滤器的优先级放到最前面
    public int getOrder() {
        return 0;
    }
}
